using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Security.Permissions;
using System.Security;
using System.Runtime.ConstrainedExecution;
using HomeDmsUtils;

namespace SendFileTo
{
    class MAPI
    {
        public bool AddRecipientTo(string email)
        {
            return AddRecipient(email, HowTo.MAPI_TO);
        }

        public bool AddRecipientCC(string email)
        {
            return AddRecipient(email, HowTo.MAPI_TO);
        }

        public bool AddRecipientBCC(string email)
        {
            return AddRecipient(email, HowTo.MAPI_TO);
        }

        public void AddAttachment(string strAttachmentFileName)
        {
            m_attachments.Add(strAttachmentFileName);
        }

        public int SendMailPopup(string strSubject, string strBody)
        {
            return SendMail(strSubject, strBody, MAPI_LOGON_UI | MAPI_DIALOG);
        }

        public int SendMailDirect(string strSubject, string strBody)
        {
            return SendMail(strSubject, strBody, MAPI_LOGON_UI);
        }

        public string[] GetEmailsFromAddressBook(IWin32Window window)
        {
            uint nNewRecips;
            SafeMapiHandle lpNewRecips;
            uint retcode = MAPIAddress(UIntPtr.Zero, new UIntPtr((uint)window.Handle), null, 1,
                null, 0, IntPtr.Zero, MapiFlags.MAPI_LOGON_UI, 0, out nNewRecips, out lpNewRecips);
            if (retcode == 0)
            {
                // prepare managed recipient object
                int size = Marshal.SizeOf(typeof(MapiRecipDesc));
                MapiRecipDesc recip = new MapiRecipDesc();

                List<string> res = new List<string>();
                for (int i = 0; i < nNewRecips; i++)
                {
                    lpNewRecips.GetMemory(i * size, recip);

                    // HACK: Outlook Express inverts the order of selected entries.
                    // We re-invert the order since OE is the most likely client.
                    int index = (int)(nNewRecips - i - 1);
                    res.Add(recip.address);
                }
                return res.ToArray();
            }
            return null;
        }

        [DllImport("MAPI32.DLL")]
        static extern int MAPISendMail(IntPtr sess, IntPtr hwnd, MapiMessage message, int flg, int rsv);

        int SendMail(string strSubject, string strBody, int how)
        {
            MapiMessage msg = new MapiMessage();
            msg.subject = strSubject;
            msg.noteText = strBody;

            msg.recips = GetRecipients(out msg.recipCount);
            msg.files = GetAttachments(out msg.fileCount);

            m_lastError = MAPISendMail(new IntPtr(0), new IntPtr(0), msg, how, 0);
            if (m_lastError > 1)
                Localizer.MsgBox(null, "MAPISendMail failed ({0}).", "MAPISendMail", MessageBoxButtons.OK, MessageBoxIcon.Error, GetLastError());

            Cleanup(ref msg);
            return m_lastError;
        }
        #region Class SafeMemoryHandle

        /// <summary>
        /// Represents a wrapper class for unmanaged memory handles.</summary>
        /// <remarks><para>
        /// <b>SafeMemoryHandle</b> is an abstract base class for <see cref="SafeHandle"/> derivatives
        /// that wrap unmanaged memory handles. All <b>SafeMemoryHandle</b> instances have an invalid
        /// handle value of <see cref="IntPtr.Zero"/> and provide (unsafe!) methods to copy data to and
        /// from arbitrary locations within their unmanaged memory blocks.
        /// </para><para>
        /// The derived classes <see cref="SafeGlobalHandle"/> and <see cref="SafeMapiHandle"/>
        /// implement <b>SafeMemoryHandle</b> for unmanaged memory blocks that are managed by
        /// <see cref="Marshal.AllocHGlobal"/> and by Simple MAPI, respectively.</para></remarks>

        public abstract class SafeMemoryHandle : SafeHandle
        {
            #region SafeMemoryHandle()

            /// <overloads>
            /// Initializes a new instance of the <see cref="SafeMemoryHandle"/> class.</overloads>
            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMemoryHandle"/> class with default
            /// parameters.</summary>
            /// <remarks>
            /// The new <b>SafeMemoryHandle</b> instance will release its handle when finalized or
            /// disposed of.</remarks>

            protected SafeMemoryHandle() : base(IntPtr.Zero, true) { }

            #endregion
            #region SafeMemoryHandle(Boolean)

            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMemoryHandle"/> class with the
            /// specified ownership flag.</summary>
            /// <param name="ownsHandle">
            /// <c>true</c> if the new instance should release its handle when finalized or disposed of;
            /// otherwise, <c>false</c>.</param>

            protected SafeMemoryHandle(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) { }

            #endregion
            #region SafeMemoryHandle(IntPtr, Boolean)

            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMemoryHandle"/> class with the
            /// specified existing handle and ownership flag.</summary>
            /// <param name="existingHandle">
            /// An <see cref="IntPtr"/> value indicating the existing handle to use.</param>
            /// <param name="ownsHandle">
            /// <c>true</c> if the new instance should release its handle when finalized or disposed of;
            /// otherwise, <c>false</c>.</param>

            protected SafeMemoryHandle(IntPtr existingHandle, bool ownsHandle) :
                base(IntPtr.Zero, ownsHandle)
            {

                SetHandle(existingHandle);
            }

            #endregion
            #region IsInvalid

            /// <summary>
            /// Gets a value indicating whether the unmanaged memory handle is invalid.</summary>
            /// <value>
            /// <c>true</c> if the unmanaged memory handle was never allocated or has already been
            /// released; otherwise, <c>false</c>.</value>
            /// <remarks>
            /// <b>IsInvalid</b> returns <c>true</c> either if the unmanaged memory handle equals <see
            /// cref="IntPtr.Zero"/>, or if <see cref="SafeHandle.IsClosed"/> is <c>true</c>.</remarks>

            public override sealed bool IsInvalid
            {
                [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
                get { return (handle == IntPtr.Zero || IsClosed); }
            }

            #endregion
            #region GetMemory(Int32, Object)

            /// <overloads>
            /// Copies the unmanaged memory block at the specified offset to a managed <see
            /// cref="Object"/>.</overloads>
            /// <summary>
            /// Copies the unmanaged memory block at the specified offset into the specified managed
            /// <see cref="Object"/>.</summary>
            /// <param name="offset">
            /// The offset from the unmanaged memory handle, in bytes, at which copying begins.</param>
            /// <param name="structure">
            /// The <see cref="Object"/> to receive the data at <paramref name="offset"/>.</param>
            /// <exception cref="ArgumentNullException">
            /// <paramref name="structure"/> is a null reference.</exception>
            /// <exception cref="ArgumentOutOfRangeException">
            /// <paramref name="offset"/> is less than zero.</exception>
            /// <exception cref="PropertyValueException">
            /// <see cref="SafeHandle.IsInvalid"/> is <c>true</c>.</exception>
            /// <remarks><para>
            /// <b>GetMemory</b> calls <see cref="Marshal.PtrToStructure"/> to copy the data at the
            /// specified <paramref name="offset"/> within the unmanaged memory block into the specified
            /// <paramref name="structure"/> which must be an instance of a formatted class.
            /// </para><note type="caution">
            /// The specified <paramref name="offset"/> is <b>not</b> checked against the (unknown) size
            /// of the unmanaged memory block. Buffer overruns are possible!</note></remarks>

            public void GetMemory(int offset, object structure)
            {
                if (IsInvalid)
                    throw new Exception("IsInvalid");

                if (offset < 0)
                    throw new Exception(
                        "offset");

                if (structure == null)
                    throw new Exception("structure");

                Marshal.PtrToStructure((IntPtr)((int)handle + offset), structure);
            }

            #endregion
            #region GetMemory(Int32, Type)

            /// <summary>
            /// Copies the unmanaged memory block at the specified offset to a managed <see
            /// cref="Object"/> of the specified type.</summary>
            /// <param name="offset">
            /// The offset from the unmanaged memory handle, in bytes, at which copying begins.</param>
            /// <param name="type">
            /// The <see cref="Type"/> of the newly allocated <see cref="Object"/> that receives the
            /// data at <paramref name="offset"/>.</param>
            /// <returns>
            /// A new <see cref="Object"/> of the specified <paramref name="type"/> containing the data
            /// at <paramref name="offset"/>.</returns>
            /// <exception cref="ArgumentNullException">
            /// <paramref name="type"/> is a null reference.</exception>
            /// <exception cref="ArgumentOutOfRangeException">
            /// <paramref name="offset"/> is less than zero.</exception>
            /// <exception cref="PropertyValueException">
            /// <see cref="SafeHandle.IsInvalid"/> is <c>true</c>.</exception>
            /// <remarks><para>
            /// <b>GetMemory</b> calls <see cref="Marshal.PtrToStructure"/> to copy the data at the
            /// specified <paramref name="offset"/> within the unmanaged memory block into a new
            /// instance the specified <paramref name="type"/> which must represent a formatted class.
            /// </para><note type="caution">
            /// The specified <paramref name="offset"/> is <b>not</b> checked against the (unknown) size
            /// of the unmanaged memory block. Buffer overruns are possible!</note></remarks>

            public object GetMemory(int offset, Type type)
            {
                if (IsInvalid)
                    throw new Exception("IsInvalid");

                if (offset < 0)
                    throw new Exception(
                        "offset");

                if (type == null)
                    throw new Exception("type");

                return Marshal.PtrToStructure((IntPtr)((int)handle + offset), type);
            }

            #endregion
            #region SetMemory

            /// <summary>
            /// Copies the data of the specified managed <see cref="Object"/> into the unmanaged memory
            /// block at the specified offset.</summary>
            /// <param name="structure">
            /// The <see cref="Object"/> containing the data to store at <paramref name="offset"/>.
            /// </param>
            /// <param name="offset">
            /// The offset from the unmanaged memory handle, in bytes, at which copying begins.</param>
            /// <param name="delete">
            /// <c>true</c> to have the <see cref="Marshal.DestroyStructure"/> method called on the
            /// unmanaged memory at <paramref name="offset"/> before copying begins. Note that passing
            /// <c>false</c> can lead to a memory leak.</param>
            /// <exception cref="ArgumentNullException">
            /// <paramref name="structure"/> is a null reference.</exception>
            /// <exception cref="ArgumentOutOfRangeException">
            /// <paramref name="offset"/> is less than zero.</exception>
            /// <exception cref="PropertyValueException">
            /// <see cref="SafeHandle.IsInvalid"/> is <c>true</c>.</exception>
            /// <remarks><para>
            /// <b>SetMemory</b> calls <see cref="Marshal.StructureToPtr"/> to copy the data of the
            /// specified <paramref name="structure"/>, which must be an instance of a formatted class,
            /// into the unmanaged memory block at the specified <paramref name="offset"/>.
            /// </para><note type="caution">
            /// The specified <paramref name="offset"/> is <b>not</b> checked against the (unknown) size
            /// of the unmanaged memory block. Buffer overruns are possible!</note></remarks>

            public void SetMemory(object structure, int offset, bool delete)
            {
                if (IsInvalid)
                    throw new Exception("IsInvalid");

                if (structure == null)
                    throw new Exception("structure");

                if (offset < 0)
                    throw new Exception(
                        "offset");

                Marshal.StructureToPtr(structure, (IntPtr)((int)handle + offset), delete);
            }

            #endregion
        }

        #endregion
        #region Class SafeMapiHandle

        /// <summary>
        /// Represents a wrapper class for unmanaged memory handles that are released by <see
        /// cref="Mapi.MAPIFreeBuffer"/>.</summary>
        /// <remarks><para>
        /// <b>SafeMapiHandle</b> provides a <see cref="SafeMemoryHandle"/> whose handle, if valid and
        /// owned, is released using the Simple MAPI function <see cref="Mapi.MAPIFreeBuffer"/>.
        /// </para><para>
        /// Simple MAPI buffers are implicitly allocated by several <see cref="Mapi"/> methods. They
        /// cannot be allocated explicitly.</para></remarks>

        public sealed class SafeMapiHandle : SafeMemoryHandle
        {
            #region SafeMapiHandle()

            /// <overloads>
            /// Initializes a new instance of the <see cref="SafeMapiHandle"/> class.</overloads>
            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMapiHandle"/> class with default
            /// parameters.</summary>
            /// <remarks>
            /// The new <b>SafeMapiHandle</b> instance will release its handle when finalized or
            /// disposed of.</remarks>

            public SafeMapiHandle() : base() { }

            #endregion
            #region SafeMapiHandle(Boolean)

            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMapiHandle"/> class with the specified
            /// ownership flag.</summary>
            /// <param name="ownsHandle">
            /// <c>true</c> if the new instance should release its handle when finalized or disposed of;
            /// otherwise, <c>false</c>.</param>

            public SafeMapiHandle(bool ownsHandle) : base(ownsHandle) { }

            #endregion
            #region SafeMapiHandle(IntPtr, Boolean)

            /// <summary>
            /// Initializes a new instance of the <see cref="SafeMapiHandle"/> class with the specified
            /// existing handle and ownership flag.</summary>
            /// <param name="existingHandle">
            /// An <see cref="IntPtr"/> value indicating the existing handle to use.</param>
            /// <param name="ownsHandle">
            /// <c>true</c> if the new instance should release its handle when finalized or disposed of;
            /// otherwise, <c>false</c>.</param>

            public SafeMapiHandle(IntPtr existingHandle, bool ownsHandle) :
                base(existingHandle, ownsHandle) { }

            #endregion
            #region ReleaseHandle

            /// <summary>
            /// Releases the unmanaged memory handle.</summary>
            /// <returns>
            /// <c>true</c> if the unmanaged memory handle was released successfully; otherwise,
            /// <c>false</c>.</returns>
            /// <remarks>
            /// <b>ReleaseHandle</b> calls <see cref="Mapi.MAPIFreeBuffer"/> to release the unmanaged
            /// memory handle, and returns <c>true</c> exactly if the resulting <see cref="MapiError"/>
            /// code equals <see cref="MapiError.SUCCESS_SUCCESS"/>.</remarks>

            protected override sealed bool ReleaseHandle()
            {
                uint error = MAPIFreeBuffer(handle);
                return (error == 0);
            }

            #endregion
        }

        #endregion
        [DllImport("mapi32.dll", CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [return: MarshalAs(UnmanagedType.U4)]
        public static extern uint MAPIFreeBuffer(IntPtr pv);

        [Flags]
        public enum MapiFlags
        {
            #region MAPI_LOGON_UI

            /// <summary>
            /// A dialog should be displayed to prompt the user to log on if required.</summary>

            MAPI_LOGON_UI = 0x00000001,

            #endregion
            #region MAPI_NEW_SESSION

            /// <summary>
            /// An attempt should be made to create a new session rather than acquire the environment's
            /// shared session.</summary>

            MAPI_NEW_SESSION = 0x00000002,

            #endregion
            #region MAPI_DIALOG

            /// <summary>
            /// A dialog should be displayed to prompt the user for recipients and other sending
            /// options.</summary>

            MAPI_DIALOG = 0x00000008,

            #endregion
            #region MAPI_EXTENDED

            /// <summary>
            /// Log on with extended capabilities.</summary>

            MAPI_EXTENDED = 0x00000020,

            #endregion
            #region MAPI_UNREAD_ONLY

            /// <summary>
            /// Only unread messages of the specified type should be enumerated.</summary>

            MAPI_UNREAD_ONLY = 0x00000020,

            #endregion
            #region MAPI_USE_DEFAULT

            /// <summary>
            /// The messaging subsystem should substitute the profile name of the default profile.
            /// </summary>

            MAPI_USE_DEFAULT = 0x00000040,

            #endregion
            #region MAPI_ENVELOPE_ONLY

            /// <summary>
            /// Only message headers should be retrieved.</summary>

            MAPI_ENVELOPE_ONLY = 0x00000040,

            #endregion
            #region MAPI_PEEK

            /// <summary>
            /// The retrieved messages should not be marked as read.</summary>

            MAPI_PEEK = 0x00000080,

            #endregion
            #region MAPI_GUARANTEE_FIFO

            /// <summary>
            /// The message identifiers returned should be in the order of time received.</summary>

            MAPI_GUARANTEE_FIFO = 0x00000100,

            #endregion
            #region MAPI_BODY_AS_FILE

            /// <summary>
            /// The message text should be sent as a file attachment.</summary>

            MAPI_BODY_AS_FILE = 0x00000200,

            #endregion
            #region MAPI_AB_NOMODIFY

            /// <summary>
            /// The address book dialog should be read-only, prohibiting changes.</summary>

            MAPI_AB_NOMODIFY = 0x00000400,

            #endregion
            #region MAPI_SUPPRESS_ATTACH

            /// <summary>
            /// The file attachments of incoming messages should not be copied.</summary>

            MAPI_SUPPRESS_ATTACH = 0x00000800,

            #endregion
            #region MAPI_FORCE_DOWNLOAD

            /// <summary>
            /// An attempt should be made to download all of the user's messages before returning.
            /// </summary>

            MAPI_FORCE_DOWNLOAD = 0x00001000,

            #endregion
            #region MAPI_LONG_MSGID

            /// <summary>
            /// The returned message identifier can be as long as 512 characters.</summary>

            MAPI_LONG_MSGID = 0x00004000,

            #endregion
            #region MAPI_PASSWORD_UI

            /// <summary>
            /// A dialog should be displayed to prompt the user for the profile password.</summary>

            MAPI_PASSWORD_UI = 0x00020000

            #endregion
        }

        [DllImport("mapi32.dll", CharSet = CharSet.Ansi)]
        [return: MarshalAs(UnmanagedType.U4)]
        public static extern uint MAPIAddress(
            UIntPtr lhSession,
            UIntPtr ulUIParam,
            [MarshalAs(UnmanagedType.LPStr)]
            string lpszCaption,
            uint nEditFields,
            [MarshalAs(UnmanagedType.LPStr)]
            string lpszLabels, // single string despite plural form!
            uint nRecips,
            IntPtr lpRecips,
            [MarshalAs(UnmanagedType.U4)]
            MapiFlags flFlags,
            uint ulReserved,
            out uint lpnNewRecips,
            out SafeMapiHandle lppNewRecips);


        bool AddRecipient(string email, HowTo howTo)
        {
		    MapiRecipDesc recipient = new MapiRecipDesc();

            recipient.recipClass = (int)howTo;
    		recipient.name = email;
		    m_recipients.Add(recipient);

            return true;
        }

        IntPtr GetRecipients(out int recipCount)
        {
            recipCount = 0;
            if (m_recipients.Count == 0)
                return IntPtr.Zero;

            int size = Marshal.SizeOf(typeof(MapiRecipDesc));
            IntPtr intPtr = Marshal.AllocHGlobal(m_recipients.Count * size);

            int ptr = (int)intPtr;
            foreach (MapiRecipDesc mapiDesc in m_recipients)
            {
                Marshal.StructureToPtr(mapiDesc, (IntPtr)ptr, false);
                ptr += size;
            }

            recipCount = m_recipients.Count;
            return intPtr;
        }

        IntPtr GetAttachments(out int fileCount)
        {
            fileCount = 0;
            if (m_attachments == null)
                return IntPtr.Zero;

            if ((m_attachments.Count <= 0) || (m_attachments.Count > maxAttachments))
                return IntPtr.Zero;

            int size = Marshal.SizeOf(typeof(MapiFileDesc));
            IntPtr intPtr = Marshal.AllocHGlobal(m_attachments.Count * size);

            MapiFileDesc mapiFileDesc = new MapiFileDesc();
            mapiFileDesc.position = -1;
            int ptr = (int)intPtr;
            
            foreach (string strAttachment in m_attachments)
            {
                mapiFileDesc.name = Path.GetFileName(strAttachment);
                mapiFileDesc.path = strAttachment;
                Marshal.StructureToPtr(mapiFileDesc, (IntPtr)ptr, false);
                ptr += size;
            }

            fileCount = m_attachments.Count;
            return intPtr;
        }

        void Cleanup(ref MapiMessage msg)
        {
            int size = Marshal.SizeOf(typeof(MapiRecipDesc));
            int ptr = 0;

            if (msg.recips != IntPtr.Zero)
            {
                ptr = (int)msg.recips;
                for (int i = 0; i < msg.recipCount; i++)
                {
                    Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiRecipDesc));
                    ptr += size;
                }
                Marshal.FreeHGlobal(msg.recips);
            }

            if (msg.files != IntPtr.Zero)
            {
                size = Marshal.SizeOf(typeof(MapiFileDesc));

                ptr = (int)msg.files;
                for (int i = 0; i < msg.fileCount; i++)
                {
                    Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiFileDesc));
                    ptr += size;
                }
                Marshal.FreeHGlobal(msg.files);
            }
            
            m_recipients.Clear();
            m_attachments.Clear();
            m_lastError = 0;
        }
        
        public string GetLastError()
		{
		    if (m_lastError <= 26)
			    return errors[ m_lastError ];
		    return "MAPI error [" + m_lastError.ToString() + "]";
		}

	    readonly string[] errors = new string[] {
		"OK [0]", "User abort [1]", "General MAPI failure [2]", "MAPI login failure [3]",
		"Disk full [4]", "Insufficient memory [5]", "Access denied [6]", "-unknown- [7]",
		"Too many sessions [8]", "Too many files were specified [9]", "Too many recipients were specified [10]", "A specified attachment was not found [11]",
		"Attachment open failure [12]", "Attachment write failure [13]", "Unknown recipient [14]", "Bad recipient type [15]",
		"No messages [16]", "Invalid message [17]", "Text too large [18]", "Invalid session [19]",
		"Type not supported [20]", "A recipient was specified ambiguously [21]", "Message in use [22]", "Network failure [23]",
		"Invalid edit fields [24]", "Invalid recipients [25]", "Not supported [26]" 
		};


        List<MapiRecipDesc> m_recipients	= new List<MapiRecipDesc>();
        List<string> m_attachments	= new List<string>();
        int m_lastError = 0;

        const int MAPI_LOGON_UI = 0x00000001;
        const int MAPI_DIALOG = 0x00000008;
        const int maxAttachments = 20;

        enum HowTo{MAPI_ORIG=0, MAPI_TO, MAPI_CC, MAPI_BCC};
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class MapiMessage
    {
        public int reserved;
        public string subject;
        public string noteText;
        public string messageType;
        public string dateReceived;
        public string conversationID;
        public int flags;
        public IntPtr originator;
        public int recipCount;
        public IntPtr recips;
        public int fileCount;
        public IntPtr files;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class MapiFileDesc
    {
        public int reserved;
        public int flags;
        public int position;
        public string path;
        public string name;
        public IntPtr type;
    }

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
    public class MapiRecipDesc
	{
	    public int		reserved;
	    public int		recipClass;
	    public string	name;
	    public string	address;
	    public int		eIDSize;
	    public IntPtr	entryID;
	}
}
